Skip to content

Support timezone-aware datetime objects #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

butson
Copy link
Contributor

@butson butson commented Apr 25, 2025

connection:

  • Added support for explicit TimeZone connection property

datatype:

  • Use Julian calendar for date/datetime before 1582 Oct 4. When the Gregorian calendar was introduced: . The day after October 4, 1582 (Julian) became October 15, 1582 (Gregorian). . That 10-day jump is not accounted for in C's calendar logic. . Julian calendar handles leap years differently. The utc calendar the engine is using understands this the python functions based on C calendar logic do not.
  • time.mktime , time.localtime don't support older dates while engine does changing to use calendar above and calculate time, avoid these issues.
  • use of datetime + timezone should handle daylight savings times and not be off by an hour.

tests:

  • Use connection property TimeZone to change timezone
  • Support timezone-aware datetime
  • Use pytz correctly with localize if pytz is used
  • Use zoneinfo.ZoneInfo if available

encodedsession:

  • Manage and verify TimeZone settings on per connection basis
  • Allows connections with different TimeZone setting from same application

requirements.txt:

  • Add requirement for tzlocal, should be available on all supported python versions. There is a change in api between releases , this is handled in the code.

@butson butson requested a review from madscientist April 25, 2025 16:52
@butson
Copy link
Contributor Author

butson commented Apr 28, 2025

I expect the cursor test failure is related to the callback to engine to get the real time zone used.

@madscientist
Copy link
Contributor

Hi @butson I will try to get to the review proper this week. Just to start out, there are a number of PEP8 violations in the new code.

What I recommend for finding them, if you don't already have an IDE which is capable of enabling flake8 or similar add-ons to do this sort of syntax checking, is to get Visual Studio Code (this is not the full Visual Studio suite, it's a stand-alone, open source editor which runs on all major plaforms: Windows, GNU/Linux, and MacOS): https://code.visualstudio.com/ . Then you should configure it for Python development by installing these extensions: Python (Microsoft), Pylance (Microsoft), and Flake8.

Once you have done that (I recommend restarting after you install the extensions) you can visit the files in your Git repository in the editor, and use CTRL-SHIFT-M to open the "Problems" window and see all the issues reported there. If there are errors not in code you didn't write/update you don't need to worry about it. In particular, the "imported but not used" messages should mostly be ignored, because these tools don't grok the "type:" comments we're using with mypy. But, there are lots of whitespace issues such as, missing spaces after commas etc. in the changed code: these need to be addressed.

@madscientist
Copy link
Contributor

Secondly, the error found by circleci in the test_result_set_gets_closed test is due to an incorrect implementation of the new method _set_timezone(). If you write it like this, instead, it will work without errors:

          stmt = self.create_statement()
          self.execute_statement(stmt, "select value from system.connectionproperties where property='TimeZone'")
          rset = self.fetch_result_set(stmt).fetchone()
          self.timezone_name = rset[0]
          self.close_statement(stmt)

Note, you can run these tests locally on your system; you need to set it up but once you do, you can verify changes before you push to GitHub and wait for the CircleCi results. If you're not sure how to do it let me know.

@madscientist
Copy link
Contributor

Thanks @butson . Note, there are a lot of mypy and pylint errors as well. You can run make mypy and make pylint to run them. You'll have to install them first (I don't know exactly how to do that on MacOS; I only use Linux myself... let me know if you can't figure it out).

On master, mypy is 100% clean and pylint only complains about a few TODO comments.

Copy link
Contributor

@madscientist madscientist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing I'm curious about is that there don't seem to be any NEW tests that test the behavior changes that were made here. It seems like only the existing tests were updated. Put another way, if we kept only the changes to the tests but did NOT add any of the other changes (except the timezone parameter of course), would all the tests pass?

The changes for the timezone parameter are good, but I don't really understand what some of the other changes are accomplishing (like the retrieval of the server zone info etc.) and I was hoping to see some tests that were making that more clear: tests that represented the missing or incorrect facilities and would fail without the changes, and then pass after the changes. Adding/retrieving dates that used to not work, but now work properly, etc.

@butson butson force-pushed the dbutson/datetime branch 2 times, most recently from 4754ab8 to c808b10 Compare May 29, 2025 16:16
@butson
Copy link
Contributor Author

butson commented May 29, 2025

Paul - I made the changes recommend here. I've updated the branch to just one commit, added test cases, and changed calendar to use the package jdcal. I've run the test on:

Python 2.7.18, 3.8.20, 3.12.8

checked the code with mypy and flake8. I've not been able to run pylint without a bunch of output from code that is not necessarily what I've updated. So any instructions for configuring and running pylint might help if there are issues there.

@butson butson force-pushed the dbutson/datetime branch from 6ec0a8d to 83aae13 Compare May 30, 2025 21:46
connection:
- Added support for explicit TimeZone connection property

datatype:
- Use Julian calendar for date/datetime before 1582 Oct 4.
  When the Gregorian calendar was introduced:
  .  The day after October 4, 1582 (Julian) became October 15, 1582 (Gregorian).
  .  That 10-day jump is not accounted for in C's calendar logic.
  .  Julian calendar handles leap years differently.
  The utc calendar the engine is using understands this the python functions
  based on C calendar logic do not.
- time.mktime , time.localtime don't support older dates while engine does
  changing to use calendar above and calculate time, avoid these issues.
- use of datetime + timezone should handle daylight savings times and not
  be off by an hour.

tests:
-  Use connection property TimeZone to change timezone
-  Support timezone-aware datetime
-  Use pytz correctly with localize if pytz is used
-  Use zoneinfo.ZoneInfo if available
-  added some more tests

encodedsession:
-  Manage and verify TimeZone settings on per connection basis
-  Allows connections with different TimeZone setting from same
   application

requirements.txt:
-  Add requirement for tzlocal and jdcal should be available on all
   supported python versions.  There is a change in
   api between releases , this is handled in the code.
@butson butson force-pushed the dbutson/datetime branch from 788ff4a to 8d34875 Compare May 30, 2025 22:25
@butson
Copy link
Contributor Author

butson commented May 30, 2025

Paul, test pass now. and ready for your review.

Copy link
Contributor

@madscientist madscientist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good, thanks especially for all the tests. Just a few things to be addressed. I will try to be more timely about future reviews.

else:
# tzlocal < 3.0
local_tz = tzlocal.get_localzone()
if local_tz:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is false, we never set params['timezone'] which means we will get an exception thrown at line 212.

But, looking at the tzinfo definition it seems like it's impossible for get_localzone() to ever return None. So, I think this if-statement at line 210 should be removed.

from .exception import DataError
from .calendar import ymd2day, day2ymd

# zoneinfo.ZoneInfo is preferred but not introduced into python3.9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean "until" rather than "into" in this comment?

params['timezone'] = tzlocal.get_localzone_name()
else:
# tzlocal < 3.0
local_tz = tzlocal.get_localzone()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use datatype.LOCALZONE here?


isP2 = sys.version[0] == '2'
TICKSDAY = 86400
LOCALZONE = tzlocal.get_localzone()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this always using get_localzone(), while in other places you check the version of python and use get_localzone_name()? Maybe that check should be collected in one place, like here, then everywhere else should use that single assignment...?

Comment on lines +176 to +182
if hasattr(tzlocal, 'get_localzone_name'):
# tzlocal >= 3.0
self.__timezone_name = tzlocal.get_localzone_name()
else:
# tzlocal < 3.0
local_tz = tzlocal.get_localzone()
self.__timezone_name = getattr(local_tz, 'zone')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it work to use datatype.LOCALZONE? I'm not sure why we need to keep repeating this code.

Comment on lines +232 to +233


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many blank lines here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks for adding these tests!

Comment on lines +142 to +143


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many blank lines here

@@ -0,0 +1,428 @@
# -*- coding: utf-8 -*-
"""
(C) Copyright 2013-2025 Dassault Systemes SE. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new file, from scratch, so your copyright year should just be this year "2025" (no range)

This uses the Georgian Calendar for dates from 10/15/1582 and
the Julian Calendar fro dates before 10/4/1582.

(C) Copyright 2013-2025 Dassault Systemes SE. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this copyright year is correct. If we wrote this ourselves, rather than copied from somewhere else, then the date should just be this year when we created the file "2025" (no range).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants